﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;

namespace Pittypat
{
    /// <summary>
    /// 日期类型。
    /// </summary>
    /// <remarks>
    /// <para>
    /// Date 内部使用从 0001/01/01 经过的天数作为刻度来表示日期值，最小刻度为 0，表示 0001/01/01，最大刻度为 3652058，表示 9999/12/31。
    /// </para>
    /// </remarks>
    [Serializable]
    internal struct DateValue : IComparable, IFormattable, IConvertible, IComparable<DateValue>, IEquatable<DateValue>
    {
        /// <summary>
        /// 最小刻度值，该值为 0。
        /// </summary>
        public const int MinTicks = 0;
        /// <summary>
        /// 最大刻度值，该值为 3652058。
        /// </summary>
        public const int MaxTicks = 3652058;

        /// <summary>
        /// 日期的最小值。
        /// </summary>
        public static readonly DateValue MinValue = new DateValue(MinTicks);

        /// <summary>
        /// 日期的最大值。
        /// </summary>
        public static readonly DateValue MaxValue = new DateValue(MaxTicks);

        private int ticks;

        /// <summary>
        /// 使用刻度值初始化 Date 结构的新实例。
        /// </summary>
        /// <param name="ticks">以天为单位的刻度值。</param>
        /// <exception cref="ArgumentOutOfRangeException">ticks 小于 MinTicks，或者大于 MaxTicks。</exception>
        public DateValue(int ticks)
        {
            if (ticks < MinTicks || ticks > MaxTicks)
            {
                throw new ArgumentOutOfRangeException("ticks", ticks, "刻度值超出了范围。");
            }

            this.ticks = ticks;
        }

        /// <summary>
        /// 使用年月日初始化 Date 结构的新实例。
        /// </summary>
        /// <param name="year">年份。</param>
        /// <param name="month">月份。</param>
        /// <param name="day">日期。</param>
        public DateValue(int year, int month, int day)
        {
            this.ticks = CalcTicks(new DateTime(year, month, day));
        }

        /// <summary>
        /// 使用指定的 DateTime 初始化 Date 结构的新实例，忽略时间部分。
        /// </summary>
        /// <param name="time">一个 DateTime 值。</param>
        public DateValue(DateTime time)
        {
            this.ticks = CalcTicks(time);
        }

        static int CalcTicks(DateTime t)
        {
            return (int)Math.Floor(t.Date.Subtract(DateTime.MinValue).TotalDays);
        }

        /// <summary>
        /// 获取此日期值的刻度数。
        /// </summary>
        public int Ticks
        {
            get
            {
                return this.ticks;
            }
        }

        /// <summary>
        /// 获取使用 DateTime 表示的此实例的值。
        /// </summary>
        public DateTime Value
        {
            get
            {
                return DateTime.MinValue.AddDays(this.ticks);
            }
        }

        /// <summary>
        /// 获取年份。
        /// </summary>
        public int Year
        {
            get
            {
                return this.Value.Year;
            }
        }

        /// <summary>
        /// 获取月份。
        /// </summary>
        public int Month
        {
            get
            {
                return this.Value.Month;
            }
        }

        /// <summary>
        /// 获取日期。
        /// </summary>
        public int Day
        {
            get
            {
                return this.Value.Day;
            }
        }

        /// <summary>
        /// 获取此实例所表示的日期是星期几。
        /// </summary>
        public DayOfWeek DayOfWeek
        {
            get
            {
                return this.Value.DayOfWeek;
            }
        }

        /// <summary>
        /// 获取此实例所表示的日期是该年中的第几天，为 1 和 366 之间的一个值。
        /// </summary>
        public int DayOfYear
        {
            get
            {
                return this.Value.DayOfYear;
            }
        }

        /// <summary>
        /// 获取当前日期。
        /// </summary>
        public static DateValue Today
        {
            get
            {
                return new DateValue(CalcTicks(DateTime.Today));
            }
        }

        /// <summary>
        /// 返回一个新的 Date，它将指定的天数加到此 Date 的值上。
        /// </summary>
        /// <param name="days">要加的天数。</param>
        /// <returns>新的 Date。</returns>
        /// <exception cref="ArgumentOutOfRangeException">所生成的 Date 小于 MinValue，或大于 MaxValue。</exception>
        public DateValue Add(int days)
        {
            return new DateValue(this.ticks + days);
        }

        /// <summary>
        /// 返回指定年和月中的天数。
        /// </summary>
        /// <param name="year">年。</param>
        /// <param name="month">月（介于 1 到 12 之间的一个数字）。</param>
        /// <returns>指定 year 中 month 的天数。</returns>
        public static int DaysInMonth(int year, int month)
        {
            return DateTime.DaysInMonth(year, month);
        }

        /// <summary>
        /// 对两个 Date 的实例进行比较，并返回一个指示第一个实例是早于、等于还是晚于第二个实例的整数。
        /// </summary>
        /// <param name="d1">第一个 Date。 </param>
        /// <param name="d2">第二个 Date。 </param>
        /// <returns>一个有符号数字，指示 d1 和 d2 的相对值。</returns>
        public static int Compare(DateValue d1, DateValue d2)
        {
            return d1.CompareTo(d2);
        }

        /// <summary>
        /// 将此实例的值与指定的 Date 值相比较，并返回一个整数，该整数指示此实例是早于、等于还是晚于指定的 Date 值。
        /// </summary>
        /// <param name="other">要比较的 Date 对象。</param>
        /// <returns>一个有符号数字，指示此实例和 other 的相对值。</returns>
        public int CompareTo(DateValue other)
        {
            return this.ticks - other.ticks;
        }

        /// <summary>
        /// 返回一个值，该值指示 Date 的两个实例是否相等。
        /// </summary>
        /// <param name="d1">第一个 Date 实例。</param>
        /// <param name="d2">第二个 Date 实例。</param>
        /// <returns>如果两个 Date 值相等，则为 true；否则为 false。</returns>
        public static bool Equals(DateValue d1, DateValue d2)
        {
            return d1.ticks == d2.ticks;
        }

        /// <summary>
        /// 返回一个值，该值指示此实例是否与指定的 Date 实例相等
        /// </summary>
        /// <param name="other">要与此实例进行比较的 Date 实例。</param>
        /// <returns>如果 value 参数等于此实例的值，则为 true；否则为 false。</returns>
        public bool Equals(DateValue other)
        {
            return this.ticks == other.ticks;
        }

        /// <summary>
        /// 将此实例的值与包含指定的 Date 值的指定对象相比较，并返回一个整数，该整数指示此实例是早于、等于还是晚于指定的 Date 值。
        /// </summary>
        /// <param name="value">要比较的 Date 装箱对象，或 null。</param>
        /// <returns>一个有符号数字，指示此实例和 value 的相对值。</returns>
        public int CompareTo(object value)
        {
            if (!(value is DateValue))
            {
                return 1;
            }

            return this.ticks - ((DateValue)value).ticks;
        }

        /// <summary>
        /// 使用指定的格式信息将当前 DateTime 对象的值转换为其等效的字符串表示形式。
        /// </summary>
        /// <param name="format">标准或自定义日期和时间格式的字符串。</param>
        /// <returns>由 format 指定的当前 DateTime 对象的值的字符串表示形式。</returns>
        public string ToString(string format)
        {
            return this.Value.ToString(String.IsNullOrEmpty(format) ? "d" : format);
        }

        /// <summary>
        /// 使用指定区域性特定格式信息将当前 Date 对象的值转换为其等效的字符串表示形式。
        /// </summary>
        /// <param name="formatProvider">一个提供区域性特定的格式设置信息的对象。</param>
        /// <returns>由 provider 指定的当前 Date 对象的值的字符串表示形式。</returns>
        public string ToString(IFormatProvider formatProvider)
        {
            if (formatProvider == null)
            {
                return this.Value.ToString("d");
            }
            else
            {
                return this.Value.ToString(formatProvider);
            }
        }

        /// <summary>
        /// 使用指定的格式和区域性特定格式信息将当前 Date 对象的值转换为其等效的字符串表示形式。
        /// </summary>
        /// <param name="format">标准或自定义日期和时间格式的字符串。</param>
        /// <param name="formatProvider">一个提供区域性特定的格式设置信息的对象。</param>
        /// <returns>由 format 和 provider 指定的当前 Date 对象的值的字符串表示形式。</returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (String.IsNullOrEmpty(format))
            {
                if (formatProvider == null)
                {
                    return this.Value.ToString("d");
                }
                else
                {
                    return this.Value.ToString(formatProvider);
                }
            }
            else
            {
                if (formatProvider == null)
                {
                    return this.Value.ToString(format);
                }
                else
                {
                    return this.Value.ToString(format, formatProvider);
                }
            }
        }

        /// <summary>
        /// 将当前 Date 对象的值转换为其等效的字符串表示形式。
        /// </summary>
        /// <returns>当前 Date 对象的值的字符串表示形式。</returns>
        public override string ToString()
        {
            return this.Value.ToString("d");
        }

        /// <summary>
        /// 返回指定的年份是否为闰年的指示。
        /// </summary>
        /// <param name="year">四位数年份。</param>
        /// <returns>如果 year 为闰年，则为 true；否则为 false。</returns>
        public static bool IsLeapYear(int year)
        {
            return DateTime.IsLeapYear(year);
        }

        /// <summary>
        /// 返回一个值，该值指示此实例是否与指定的对象相等。
        /// </summary>
        /// <param name="obj">要与此实例进行比较的对象。</param>
        /// <returns>如果 value 是 Date 的实例并且等于此实例的值，则为 true；否则为 false。 </returns>
        public override bool Equals(object obj)
        {
            if (!(obj is DateValue))
            {
                return false;
            }

            return this.ticks == ((DateValue)obj).ticks;
        }

        /// <summary>
        /// 返回此实例的哈希代码。
        /// </summary>
        /// <returns>32 位有符号整数哈希代码。</returns>
        public override int GetHashCode()
        {
            return this.ticks;
        }

        #region IConvertible Members

        TypeCode IConvertible.GetTypeCode()
        {
            return TypeCode.Object;
        }

        bool IConvertible.ToBoolean(IFormatProvider provider)
        {
            return this.ticks != 0;
        }

        byte IConvertible.ToByte(IFormatProvider provider)
        {
            return (byte)this.ticks;
        }

        char IConvertible.ToChar(IFormatProvider provider)
        {
            return (char)this.ticks;
        }

        DateTime IConvertible.ToDateTime(IFormatProvider provider)
        {
            return this.Value;
        }

        decimal IConvertible.ToDecimal(IFormatProvider provider)
        {
            return this.ticks;
        }

        double IConvertible.ToDouble(IFormatProvider provider)
        {
            return this.ticks;
        }

        short IConvertible.ToInt16(IFormatProvider provider)
        {
            return (short)this.ticks;
        }

        int IConvertible.ToInt32(IFormatProvider provider)
        {
            return this.ticks;
        }

        long IConvertible.ToInt64(IFormatProvider provider)
        {
            return this.ticks;
        }

        sbyte IConvertible.ToSByte(IFormatProvider provider)
        {
            return (sbyte)this.ticks;
        }

        float IConvertible.ToSingle(IFormatProvider provider)
        {
            return this.ticks;
        }

        string IConvertible.ToString(IFormatProvider provider)
        {
            return this.ToString(provider);
        }

        object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        {
            return ((IConvertible)this.Value).ToType(conversionType, provider);
        }

        ushort IConvertible.ToUInt16(IFormatProvider provider)
        {
            return (ushort)this.ticks;
        }

        uint IConvertible.ToUInt32(IFormatProvider provider)
        {
            return (uint)this.ticks;
        }

        ulong IConvertible.ToUInt64(IFormatProvider provider)
        {
            return (ulong)this.ticks;
        }

        #endregion

        /// <summary>
        /// 将日期和时间的指定字符串表示形式转换为其等效的 Date。
        /// </summary>
        /// <param name="s">包含要转换的日期和时间的字符串。</param>
        /// <returns>等效于 s 中包含的日期和时间的 Date。</returns>
        public static DateValue Parse(string s)
        {
            return new DateValue(DateTime.Parse(s));
        }

        /// <summary>
        /// 使用指定的区域性特定格式信息，将日期和时间的指定字符串表示形式转换为其等效的 Date。
        /// </summary>
        /// <param name="s">包含要转换的日期和时间的字符串。</param>
        /// <param name="provider">一个对象，提供有关 s 的区域性特定格式信息。</param>
        /// <returns>Date，等效于由 provider 所指定的 s 中包含的日期和时间。</returns>
        public static DateValue Parse(string s, IFormatProvider provider)
        {
            return new DateValue(DateTime.Parse(s, provider));
        }

        /// <summary>
        /// 将日期和时间的指定字符串表示形式转换为其 DateTime 等效项，并返回一个指示转换是否成功的值。
        /// </summary>
        /// <param name="s">包含要转换的日期和时间的字符串。</param>
        /// <param name="value">当此方法返回时，如果转换成功，则包含与 s 中包含的日期和时间等效的 DateTime 值；如果转换失败，则为 MinValue。 如果 s 参数为 null，是空字符串 ("") 或者不包含日期和时间的有效字符串表示形式，则转换失败。 该参数未经初始化即被传递。</param>
        /// <returns>如果 s 参数成功转换，则为 true；否则为 false。</returns>
        public static bool TryParse(string s, out DateValue value)
        {
            DateTime t = DateTime.MinValue;
            if (DateTime.TryParse(s, out t))
            {
                value = new DateValue(t);
                return true;
            }
            else
            {
                value = DateValue.MinValue;
                return false;
            }
        }

        /// <summary>
        /// 从此实例中减去指定的日期。
        /// </summary>
        /// <param name="date">Date 的一个实例。</param>
        /// <returns>两个日期相隔的天数。</returns>
        public int Subtract(DateValue date)
        {
            return this.ticks - date.ticks;
        }

        #region Operators

        /// <summary>
        /// 比较两个 Date 确定它们是否相等。
        /// </summary>
        /// <param name="x">第一个 Date。</param>
        /// <param name="y">第二个 Date。</param>
        /// <returns>如果 x 与 y 的长度相同，并且每一个数组项目也相同，则返回 true，否则返回 false。</returns>
        public static bool operator ==(DateValue x, DateValue y)
        {
            return x.CompareTo(y) == 0;
        }

        /// <summary>
        /// 比较两个 Date 确定它们是否不相等。
        /// </summary>
        /// <param name="x">第一个 Date。</param>
        /// <param name="y">第二个 Date。</param>
        /// <returns>如果 x 与 y 的长度相同，并且每一个数组项目也相同，则返回 false，否则返回 true。</returns>
        public static bool operator !=(DateValue x, DateValue y)
        {
            return !(x == y);
        }

        /// <summary>
        /// 对两个 Date 结构进行比较，以确定第一个结构是否大于第二个结构。
        /// </summary>
        /// <param name="x">一个 Date 对象。</param>
        /// <param name="y">一个 Date 对象。</param>
        /// <returns>如果第一个实例大于第二个实例，则它为 true。 否则为 false。</returns>
        public static bool operator >(DateValue x, DateValue y)
        {
            return x.CompareTo(y) > 0;
        }

        /// <summary>
        /// 对两个 Date 结构进行比较，以确定第一个结构是否大于等于第二个结构。
        /// </summary>
        /// <param name="x">一个 Date 对象。</param>
        /// <param name="y">一个 Date 对象。</param>
        /// <returns>如果第一个实例大于等于第二个实例，则它为 true。 否则为 false。</returns>
        public static bool operator >=(DateValue x, DateValue y)
        {
            return x.CompareTo(y) >= 0;
        }

        /// <summary>
        /// 对两个 Date 结构进行比较，以确定第一个结构是否小于第二个结构。
        /// </summary>
        /// <param name="x">一个 Date 对象。</param>
        /// <param name="y">一个 Date 对象。</param>
        /// <returns>如果第一个实例小于第二个实例，则它为 true。 否则为 false。</returns>
        public static bool operator <(DateValue x, DateValue y)
        {
            return x.CompareTo(y) < 0;
        }

        /// <summary>
        /// 对两个 Date 结构进行比较，以确定第一个结构是否小于等于第二个结构。
        /// </summary>
        /// <param name="x">一个 Date 对象。</param>
        /// <param name="y">一个 Date 对象。</param>
        /// <returns>如果第一个实例小于等于第二个实例，则它为 true。 否则为 false。</returns>
        public static bool operator <=(DateValue x, DateValue y)
        {
            return x.CompareTo(y) <= 0;
        }

        /// <summary>
        /// 将 Date 转换为 DateTime。
        /// </summary>
        /// <param name="value">一个 Date 对象实例。</param>
        /// <returns>转换后的 DateTime。</returns>
        public static explicit operator DateTime(DateValue value)
        {
            return value.Value;
        }

        /// <summary>
        /// 将指定的 DateTime 转换为 Date 类型。
        /// </summary>
        /// <param name="value">一个 DateTime。</param>
        /// <returns>转换后的 Date。</returns>
        public static implicit operator DateValue(DateTime value)
        {
            return new DateValue(value);
        }

        /// <summary>
        /// 计算 Date 结构与 DataTimeSpan 的和。 
        /// </summary>
        /// <param name="x">一个 Date 结构。</param>
        /// <param name="t">一个 Int32 结构。</param>
        /// <returns>一个 Date 结构，它的 Value 属性包含两个操作数的和。</returns>
        public static DateValue operator +(DateValue x, int t)
        {
            return new DateValue(x.ticks + t);
        }

        /// <summary>
        /// 从第一个 Date 操作数中减去第二个操作数。
        /// </summary>
        /// <param name="x">一个 Date 结构。</param>
        /// <param name="y">一个 Date 结构。</param>
        /// <returns>从第一个 Date 操作数中减去第二个操作数所得的结果。</returns>
        public static int operator -(DateValue x, DateValue y)
        {
            return x.ticks - y.ticks;
        }

        #endregion
    }
}
